// @Author EthanScriptOn
// @Desc
package config_field_cache

import (
	"errors"
	"fmt"
	"gitee.com/fatzeng/srf_switch_basic_components/util"
	"reflect"
	"strings"
)

type FieldCache struct {
	fieldName     string
	tagList       map[string]string
	field         reflect.Value
	index         int
	isCover       bool
	fieldCacheMap map[string]*FieldCache
}

func (f *FieldCache) GetFieldName() string {
	return f.fieldName
}

func (f *FieldCache) GetFieldCacheMap() map[string]*FieldCache {
	return f.fieldCacheMap
}

func (f *FieldCache) GetTagList() map[string]string {
	return f.tagList
}

func (f *FieldCache) GetIndex() int {
	return f.index
}

func (f *FieldCache) IsCover() bool {
	return f.isCover
}

func (f *FieldCache) IsValid() error {
	if f.fieldName == "" {
		return errors.New("field name is empty")
	}
	if !f.field.IsValid() {
		return errors.New("field is invalid")
	}
	if len(f.tagList) == 0 {
		return errors.New("tag list is empty")
	}
	return nil
}

func (f *FieldCache) DeepCopy() *FieldCache {
	fieldCacheMap := make(map[string]*FieldCache)
	for key, cache := range f.fieldCacheMap {
		deepCopy := cache.DeepCopy()
		fieldCacheMap[key] = deepCopy
	}
	return &FieldCache{
		fieldName:     f.fieldName,
		tagList:       f.tagList,
		field:         util.NewInstance(f.field.Type()),
		index:         f.index,
		fieldCacheMap: fieldCacheMap,
	}
}

func GetFieldCache(structPtr interface{}) (cacheMapping map[string]*FieldCache, err error) {
	if structPtr == nil {
		return nil, errors.New("the param is nil")
	}
	if reflect.TypeOf(structPtr).Kind() != reflect.Ptr || reflect.TypeOf(reflect.ValueOf(structPtr).Elem().Interface()).Kind() != reflect.Struct {
		return nil, errors.New("the parameter must be a pointer of struct type")
	}
	fieldCacheMap := make(map[string]*FieldCache)
	val := reflect.ValueOf(structPtr)
	cacheSlice, err := assembleFieldCache(nil, &val, 0)
	if err != nil {
		return nil, err
	}
	for _, cacheTemp := range cacheSlice {
		cache := cacheTemp
		if err = cache.IsValid(); err != nil {
			return nil, err
		}
		fieldCacheMap[cache.fieldName] = cache
	}
	return fieldCacheMap, err
}

func assembleFieldCache(fieldStruct *reflect.StructField, fieldValue *reflect.Value, index int) (cacheSlice []*FieldCache, err error) {
	defer util.HandleErr(&err)
	if fieldValue == nil {
		return cacheSlice, errors.New("the parameter fieldValue must not nil")
	}
	rVal := *fieldValue
	if rVal.Kind() == reflect.Ptr && !rVal.IsNil() {
		rVal = rVal.Elem()
	}
	vElems := rVal
	tElems := vElems.Type()
	cacheSliceContainer := make([]*FieldCache, 0)
	switch tElems.Kind() {
	case reflect.Map:
		return cacheSliceContainer, fmt.Errorf("the field %s is a map, which is not supported", tElems.Name())
	case reflect.Struct:
	roundMatch:
		for roundIndex := 0; roundIndex < tElems.NumField(); roundIndex++ {
			roundFieldStruct := tElems.Field(roundIndex)
			roundFieldValue := vElems.Field(roundIndex)
			fCache := generateFieldCache(&roundFieldStruct, &roundFieldValue, roundIndex)
			cacheSliceContainer = append(cacheSliceContainer, fCache)
			matchValue := fCache.field
		typeMatch:
			for {
				switch matchValue.Kind() {
				case reflect.Ptr:
					if err = notSupport(matchValue.Type().Elem().Kind(), reflect.Ptr, reflect.Slice, reflect.Map); err != nil {
						return cacheSliceContainer, fmt.Errorf("the field %s has error: [%v]", fCache.fieldName, err.Error())
					}
					switch matchValue.Type().Elem().Kind() {
					case reflect.Struct:
						matchTypeElem := matchValue.Type().Elem()
						ptrInstance := reflect.New(matchTypeElem).Interface()
						matchValue.Set(reflect.ValueOf(ptrInstance))
						matchValue = reflect.ValueOf(ptrInstance).Elem()
						continue
					default:
						matchValue = matchValue.Elem()
						continue
					}
				case reflect.Slice:
					if err = notSupport(matchValue.Type().Elem().Kind(), reflect.Slice, reflect.Map); err != nil {
						return cacheSliceContainer, fmt.Errorf("the field %s has error: [%v]", fCache.fieldName, err.Error())
					}
					fCache.isCover = false
					switch matchValue.Type().Elem().Kind() {
					case reflect.Ptr:
						emptyInstancePtr := util.NewInstance(matchValue.Type().Elem())
						instance := reflect.New(matchValue.Type().Elem().Elem()).Interface()
						emptyInstancePtr.Set(reflect.ValueOf(instance))
						matchValue = reflect.ValueOf(instance).Elem()
						continue
					case reflect.Struct:
						emptyInstance := util.NewInstance(matchValue.Type().Elem())
						instance := reflect.New(matchValue.Type().Elem()).Elem().Interface()
						emptyInstance.Set(reflect.ValueOf(instance))
						matchValue = emptyInstance
						continue
					}
					continue roundMatch
				case reflect.Struct:
					break typeMatch
				default:
					continue roundMatch
				}
			}
			roundCacheSlice, roundErr := assembleFieldCache(nil, &matchValue, roundIndex)
			if roundErr != nil {
				return cacheSliceContainer, roundErr
			}
			for _, cacheTemp := range roundCacheSlice {
				cache := cacheTemp
				if err = cache.IsValid(); err != nil {
					return cacheSliceContainer, err
				}
				fCache.fieldCacheMap[cache.fieldName] = cache
			}
		}
	default:
		if fieldStruct == nil {
			return cacheSliceContainer, errors.New("the parameter fieldStruct must not nil")
		}
		fCache := generateFieldCache(fieldStruct, fieldValue, index)
		cacheSliceContainer = append(cacheSliceContainer, fCache)
	}
	return cacheSliceContainer, err
}

func notSupport(targetKind reflect.Kind, kinds ...reflect.Kind) error {
	for _, kind := range kinds {
		if targetKind == kind {
			return fmt.Errorf("the kind is [%v], which is not supported", targetKind)
		}
	}
	return nil
}

func support(targetKind reflect.Kind, kinds ...reflect.Kind) error {
	for _, kind := range kinds {
		if targetKind != kind {
			return fmt.Errorf("the kind is [%v], which is not supported", targetKind)
		}
	}
	return nil
}

func generateFieldCache(fieldStruct *reflect.StructField, fieldValue *reflect.Value, index int) *FieldCache {
	fCache := new(FieldCache)
	fCache.tagList = splitTag(fieldStruct.Tag)
	fCache.fieldName = fieldStruct.Name
	fCache.index = index
	fCache.fieldCacheMap = make(map[string]*FieldCache)
	fCache.field = *fieldValue
	fCache.isCover = true
	return fCache
}

func splitTag(tag reflect.StructTag) map[string]string {
	tagList := make(map[string]string)
	split := strings.Split(string(tag), " ")
	if len(split) <= 0 {
		return tagList
	}
	for _, singleVal := range split {
		singleValSlice := strings.Split(singleVal, ":")
		if len(singleValSlice) != 2 {
			continue
		}
		key := singleValSlice[0]
		val := strings.Trim(singleValSlice[1], "\"")
		tagList[key] = val
	}
	return tagList
}

func (f *FieldCache) foundInjectValue(beInjectedData map[string]interface{}, parseType string) (tagVal interface{}, err error) {
	tag, ok := f.tagList[parseType]
	if !ok {
		return nil, fmt.Errorf("the tag %s is not found", parseType)
	}
	tagVal, ok = beInjectedData[tag]
	if !ok {
		return nil, fmt.Errorf("the tag value %s is not found", tag)
	}
	return
}

func (f *FieldCache) FieldFilling(beInjectedData map[string]interface{}, parseType string) (err error) {
	defer util.HandleErr(&err)
	tagVal, err := f.foundInjectValue(beInjectedData, parseType)
	if err != nil {
		return err
	}
	return f.instanceParsing(tagVal, parseType)
}

func (f *FieldCache) FieldFillingWithInjectValue(tagVal interface{}, parseType string) (err error) {
	defer util.HandleErr(&err)
	return f.instanceParsing(tagVal, parseType)
}

func (f *FieldCache) instanceParsing(tagVal interface{}, parseType string) (err error) {
	roundCache := f
	switch tagVal.(type) {
	case []map[string]interface{}:
		if err = support(roundCache.field.Type().Kind(), reflect.Slice); err != nil {
			return fmt.Errorf("the field %s has error: [%v]", roundCache.fieldName, err.Error())
		}
		roundTagValues := tagVal.([]map[string]interface{})
		for _, roundTagVal := range roundTagValues {
			sliceIndexValue := util.NewInstance(roundCache.field.Type().Elem())
			sliceIndexType := sliceIndexValue.Type()
			var valueInstance reflect.Value
			var elem reflect.Type
			if sliceIndexType.Kind() == reflect.Ptr && sliceIndexType.Elem().Kind() == reflect.Struct {
				valueInstance = util.NewInstance(sliceIndexType.Elem())
				sliceIndexValue.Set(valueInstance.Addr())
				elem = sliceIndexType.Elem()
			} else if sliceIndexType.Kind() == reflect.Struct {
				valueInstance = sliceIndexValue
				elem = sliceIndexType
			} else {
				//
			}
			for _, cache := range roundCache.fieldCacheMap {
				deepCopy := cache.DeepCopy()
				cacheValue, err := deepCopy.foundInjectValue(roundTagVal, parseType)
				if err != nil {
					return err
				}
				name, canFound := elem.FieldByName(deepCopy.fieldName)
				if !canFound || name.PkgPath != "" {
					return fmt.Errorf("the field %s is not found", deepCopy.fieldName)
				}
				if err = deepCopy.instanceParsing(cacheValue, parseType); err != nil {
					return err
				}
				valueInstance.FieldByIndex(name.Index).Set(deepCopy.field)
			}
			roundCache.field.Set(reflect.Append(roundCache.field, sliceIndexValue))
		}
	case map[string]interface{}:
		roundTagVal := tagVal.(map[string]interface{})
		for _, cache := range roundCache.fieldCacheMap {
			if cacheValue, err := cache.foundInjectValue(roundTagVal, parseType); err != nil {
				return err
			} else {
				if err = cache.instanceParsing(cacheValue, parseType); err != nil {
					return err
				}
			}
		}
		return
	default:
		if len(roundCache.fieldCacheMap) == 0 && roundCache.field.CanSet() {
			roundCache.field.Set(reflect.ValueOf(tagVal))
		}
	}
	return
}
